#pragma once

#include "bluetooth.h"
#include "hci.h"
#include "hci_lib.h"

#include "common.hpp"
#include <atomic>
#include <condition_variable>
#include <functional>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

#include <sys/ioctl.h>
#include <unistd.h>

enum class BlueState_t : uint8_t {
    WAIT_SCAN,  /* 等待扫描 */
    START_SCAN, /* 开始扫描 */
    SCANNING,   /* 正在扫描 */
    STOP_SCAN,  /* 停止扫描 */
};

/* use hci api process */
class BlueTooth {
public:
    using BtName_t = const std::string &;
    using BtAddr_t = const std::string &;
    using OnLeScanAction_t = std::function<void(BtName_t, BtAddr_t)>;

private:
    int _devId;
    int _fd;

    std::vector<OnLeScanAction_t> onLeScanAction;

    std::mutex _mutex;
    std::condition_variable _cv;
    BlueState_t state;
    hci_filter oldFilter;

private:
    std::string getName(uint8_t *eir, std::size_t eirLen);
    std::string getAddr(const bdaddr_t *ba);

    bool up(void); /* up hci0 device */

    void blueToothStateMachine(void);

    void onStartLeScan(BlueState_t &s);
    void onLeScaning(BlueState_t &s);
    void onStopLeScan(BlueState_t &s);

public:
    BlueTooth();

    bool leScanStart(void);
    void leScanStop(void);

    void registerOnLeScan(const std::function<void(BtName_t, BtAddr_t)> &f);
};

BlueTooth::BlueTooth()
    : _devId(-1)
    , _fd(-1)
{
    state = BlueState_t::WAIT_SCAN;
    std::thread(&BlueTooth::blueToothStateMachine, this).detach();
}

void BlueTooth::blueToothStateMachine(void)
{
    while (true) {
        std::unique_lock<std::mutex> _lock(_mutex);
        _cv.wait(_lock, [&] { return state != BlueState_t::WAIT_SCAN; });

        switch (state) {
            case BlueState_t::START_SCAN: {
                onStartLeScan(state);
            } break;

            case BlueState_t::SCANNING: {
                onLeScaning(state);
            } break;

            case BlueState_t::STOP_SCAN: {
                onStopLeScan(state);
            } break;

            default:
                break;
        }
    }
}

bool BlueTooth::leScanStart(void)
{
    if (state != BlueState_t::WAIT_SCAN) {
        bluetooth_warn("ble scan is scaning, wait");
        return true;
    }

    state = BlueState_t::START_SCAN;
    _cv.notify_one();
    return true;
}

void BlueTooth::onStartLeScan(BlueState_t &s)
{
    up();
    _fd = hci_open_dev(_devId);
    if (_fd < 0) {
        bluetooth_error("open bluetooth dev failed!!!");
        return;
    }

    hci_le_set_scan_parameters(_fd, 0x01, htobs(0x0010), htobs(0x0010), 0x00, 0x00, 1000);
    hci_le_set_scan_enable(_fd, 0x01, 0x01, 1000);

    hci_filter nf {0};
    hci_filter_clear(&nf);
    hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
    hci_filter_set_event(EVT_LE_META_EVENT, &nf);

    socklen_t olen = sizeof(oldFilter);

    if (getsockopt(_fd, SOL_HCI, HCI_FILTER, &oldFilter, &olen) < 0) {
        bluetooth_error("Could not get bluetooth socket options");
    } else {
        bluetooth_info("get bluetooth socket option success");
    }

    if (setsockopt(_fd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) {
        bluetooth_error("Could not set bluetooth socket options");
    } else {
        bluetooth_info("set bluetooth socket option success");
    }

    s = BlueState_t::SCANNING;
}

void BlueTooth::onLeScaning(BlueState_t &s)
{
    unsigned char buf[HCI_MAX_EVENT_SIZE] {0}, *ptr = nullptr;
    evt_le_meta_event *meta = nullptr;
    le_advertising_info *info = nullptr;
    std::size_t len = read(_fd, buf, sizeof(buf));
    if (len < 0) {
        ("read bluetooth socket error");
        return;
    }

    ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
    len -= (1 + HCI_EVENT_HDR_SIZE);

    meta = (evt_le_meta_event *)ptr;

    if (meta->subevent != 0x02) {
        return;
    }

    info = (le_advertising_info *)(meta->data + 1);

    std::for_each(onLeScanAction.begin(), onLeScanAction.end(), [&](const OnLeScanAction_t &action) {if (action){action(getName(info->data, info->length), getAddr(&info->bdaddr));} });
}

void BlueTooth::onStopLeScan(BlueState_t &s)
{
    if (setsockopt(_fd, SOL_HCI, HCI_FILTER, &oldFilter, sizeof(oldFilter)) < 0) {
        bluetooth_error("Could not set bluetooth socket options");
    } else {
        bluetooth_info("set bluetooth socket option success");
    }

    /* uint8_t enable ? enable : disable */
    hci_le_set_scan_enable(_fd, 0x00, 0x01, 1000);

    hci_close_dev(_fd);

    s = BlueState_t::WAIT_SCAN;
}

void BlueTooth::leScanStop(void)
{
    if (state == BlueState_t::STOP_SCAN) {
        return;
    }

    state = BlueState_t::STOP_SCAN;
    _cv.notify_one();
}

void BlueTooth::registerOnLeScan(const std::function<void(BtName_t, BtAddr_t)> &f)
{
    onLeScanAction.emplace_back(f);
}

std::string BlueTooth::getName(uint8_t *eir, size_t eirLen)
{
    char buf[64] {0};
    size_t offset = 0;

    while (offset < eirLen) {
        uint8_t fieldLen = eir[0];

        if (!fieldLen || offset + fieldLen > eirLen) {
            return "(unknown)";
        }

#define EIR_NAME_SHORT    0x08 /* shortened local name */
#define EIR_NAME_COMPLETE 0x09 /* complete local name */

        if (eir[1] == EIR_NAME_SHORT || eir[1] == EIR_NAME_COMPLETE) {
            size_t nameLen = fieldLen - 1;
            if (nameLen > sizeof(buf) - 1) {
                return "(unknown)";
            }

            memcpy(buf, &eir[2], nameLen);
            return std::string(buf);
        }
        offset += fieldLen + 1;
        eir += fieldLen + 1;
    }

    return "(unknown)";
}

std::string BlueTooth::getAddr(const bdaddr_t *ba)
{
    char addr[18] {};
    ba2str(ba, addr);
    return std::string(addr);
}

bool BlueTooth::up(void)
{
    _devId = hci_get_route(nullptr);
    if (_devId >= 0) {
        return true;
    }

    _devId = 0; /* hci0 */
    bluetooth_warn("the bluetooth hci0 not up, prepare up...");

    int fd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
    if (fd < 0) {
        bluetooth_error("open bluetooth socket error --> {}", strerror(errno));
        return false;
    }

    if (ioctl(fd, HCIDEVUP, _devId) < 0) {
        bluetooth_error("hci{} up failed!!!", _devId);
        close(fd);
        return false;
    }

    bluetooth_error("hci{} up success!!!", _devId);
    close(fd);
    return true;
}
